package distributed

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/go-redis/redis/v8"
	"github.com/google/uuid"
)

// RedisLock Redis 分布式锁
type RedisLock struct {
	client   *redis.Client
	key      string
	value    string
	expiry   time.Duration
	ctx      context.Context
	cancelFn context.CancelFunc
}

// NewRedisLock 创建 Redis 分布式锁
func NewRedisLock(client *redis.Client, key string, expiry time.Duration) *RedisLock {
	ctx, cancel := context.WithCancel(context.Background())
	return &RedisLock{
		client:   client,
		key:      key,
		value:    uuid.New().String(), // 使用 UUID 作为锁的值，防止误释放
		expiry:   expiry,
		ctx:      ctx,
		cancelFn: cancel,
	}
}

// TryLock 尝试获取锁（非阻塞）
func (l *RedisLock) TryLock() (bool, error) {
	// 使用 SET NX EX 命令：如果 key 不存在则设置，并设置过期时间
	result, err := l.client.SetNX(l.ctx, l.key, l.value, l.expiry).Result()
	if err != nil {
		return false, fmt.Errorf("failed to acquire lock: %w", err)
	}

	// 如果获取到锁，启动自动续期
	if result {
		go l.autoRenew()
	}

	return result, nil
}

// Unlock 释放锁
func (l *RedisLock) Unlock() error {
	// 取消自动续期
	l.cancelFn()

	// 使用 Lua 脚本保证原子性：只有持有锁的实例才能释放
	script := `
		if redis.call("get", KEYS[1]) == ARGV[1] then
			return redis.call("del", KEYS[1])
		else
			return 0
		end
	`

	result, err := l.client.Eval(l.ctx, script, []string{l.key}, l.value).Result()
	if err != nil {
		return fmt.Errorf("failed to release lock: %w", err)
	}

	if result == int64(0) {
		log.Printf("[RedisLock] Lock %s was not held by this instance", l.key)
	}

	return nil
}

// autoRenew 自动续期锁（每隔 expiry/3 续期一次）
func (l *RedisLock) autoRenew() {
	ticker := time.NewTicker(l.expiry / 3)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			// 使用 Lua 脚本续期：只有持有锁的实例才能续期
			script := `
				if redis.call("get", KEYS[1]) == ARGV[1] then
					return redis.call("expire", KEYS[1], ARGV[2])
				else
					return 0
				end
			`

			result, err := l.client.Eval(l.ctx, script, []string{l.key}, l.value, int(l.expiry.Seconds())).Result()
			if err != nil {
				log.Printf("[RedisLock] Failed to renew lock %s: %v", l.key, err)
				return
			}

			if result == int64(0) {
				log.Printf("[RedisLock] Lost lock %s, stopping auto-renew", l.key)
				return
			}

		case <-l.ctx.Done():
			return
		}
	}
}

// IsLocked 检查锁是否存在
func (l *RedisLock) IsLocked() (bool, error) {
	result, err := l.client.Exists(l.ctx, l.key).Result()
	if err != nil {
		return false, err
	}
	return result > 0, nil
}
